#!/usr/bin/env python


import _pg, pg
import re, datetime, cvs

	
def goToRightParen(i, tokens):
	i += 1
	while tokens[i] != ')':
		if(i+1 >= len(tokens)):
			#this means we got some invalid function header input
			raise ValueError, 'problem parsing function interface, must be a bad function'
		if tokens[i] == '(':
			i = goToRightParen(i, tokens)
			
		i += 1
	return i
		
def parseCTokens(line):
	"parse a C function into a list of tokens."
	#deal with escaped characters
	line = re.sub('\*', ' * ', line)
	line = re.sub( '\)', ' ) ', line)
	line = re.sub( '\(', ' ( ', line)
	#space out characters for tokenizing
	line = re.sub( ',', ' , ', line)
	line = re.sub('\n', ' ', line)
	
	lineTokens = re.split('\s+', line)
	if lineTokens[-1] == '':
		lineTokens = lineTokens[:-1]
		
	parsedTokens = []

	#record tokens inside the interface
	#format is: startFirst startNext (fieldStart fieldNext, field start fieldNext...)
	#consider ignoring a #define???
	startFirst, startNext, fieldStart, fieldNext = range(4)
	state = startFirst
	i = 0
	while i < len(lineTokens):
		if state == startFirst:
			#ignore static and const
			if lineTokens[i] == 'static':
				pass
			elif lineTokens[i] == 'const':
				pass		
			else:
				parsedTokens.append(lineTokens[i])
				state = startNext
		elif state == startNext:
			if lineTokens[i] == '(':
				if i == 1:
					#in this case, we're in a macro return type
					#suck in everything to the next parenthesis
					level = 1
					while i < len(lineTokens) and level > 0:
						parsedTokens.append(lineTokens[i])
						if lineTokens[i] == '(' and i > 1:
							level += 1
						elif lineTokens[i] == ')':
							level -= 1
						i += 1
					#when we hit the last ), we then incrament i one too many
					i -= 1
				else:
					#this must be the start of the arguments
					parsedTokens.append(lineTokens[i])
					state = fieldStart
				#	i += 1
				#suck in right parenthesis
				#if i < len(lineTokens):
				#	parsedTokens.append(lineTokens[i])
					
			else:
				parsedTokens.append(lineTokens[i])
				
		elif state == fieldStart:
			if lineTokens[i] == 'struct':
				pass
			elif lineTokens[i] == 'const':
				pass
			else:
				parsedTokens.append(lineTokens[i])
				state = fieldNext
		elif state == fieldNext:
			if lineTokens[i] == ')':
				parsedTokens.append(lineTokens[i])
			elif lineTokens[i] == '(':
				#parse to next )
				i = goToRightParen(i, lineTokens)
			elif lineTokens[i] == ',':
				parsedTokens.append(lineTokens[i])	
				state = fieldStart
			elif lineTokens[i] == '*':
				parsedTokens.append(lineTokens[i])
			else:
				#this is an argument, don't record it 
				pass
				
		i += 1
	return parsedTokens
			
	
def getCInterfaceTokens(sourceFile, functions):
	"""takes in list of functions FROM THE SAME FILE with the order being INCREASING LINE ORDER, fills in interface data into the functions. 
	"""
	returnFunctions = []
	lines = sourceFile.readlines()

	for funcNum in range(len(functions)):
		i = functions[funcNum].startLine - 1 #python starts at 0, not 1
		print "parsing function at line: ", i
		try:
			firstLine = lines[i]
		except IndexError:
			print "*** error linenumber:", i, "is out of range in the file:", sourceFile.name
			sys.exit()
		
		firstLineNum = i

		#parse out the function declaration all the way to before the {
		#see error case below for when we follow a bad function lead into the next funtion or eof
		line = ''
		foundBrace = 1
		while(lines[i].find('{') == -1): #while not on the last line of the function declaration
			
			line += lines[i]
			
			i+=1
			if(i >= len(lines)):
				print "*** error, could not find end of interface before eof, firstline:", firstLine, " on line:", firstLineNum		
				print "*** infile:", sourceFile.name
				foundBrace = 0
				break

			if (funcNum + 1 < len(functions) and i > functions[funcNum + 1].startLine):
				print "*** error, could not find end of interface before next function, firstline:", firstLine, "on:", firstLineNum, "before function on line:", functions[funcNum + 1].startLine
				print "*** infile:", sourceFile.name
				foundBrace = 0
				break	#we either leaked into the next funtion or tried to read past the file. This happens when we have incorrect values for the start of functions.
		if foundBrace:	
			
			line += lines[i][:lines[i].find('{')]
			
			#kill new lines and block comments
			line = re.sub('\n', ' ', line)
			line = re.sub('/\*.*\*/', ' ', line)
			
			
			######## parse function header token list ########
			print "line before tokenizer:", line
			try:
				print "line:", line, "tokens:", parseCTokens(line)
				functions[funcNum].interface = parseCTokens(line)
				
				returnFunctions.append(functions[funcNum])
			except ValueError, e:
				print e			
	return returnFunctions
		
		
	
def getMonthList(firstDate, lastDate):
	"takes in a start and end datetime, returning a list of datetimes (start, one month, two months, ... , last month before lastDate)"		
	dates = []
	currentDate = firstDate
	while(currentDate < lastDate):
		dates.append(currentDate)
		currentDate = currentDate + datetime.timedelta(30)
	return dates

#as of July 2006, this function was not used. Functionality moved to cscope.py
def fillInterfaceData(db, CVSPATH):
	"scan the source code, store interface data in sql"
	
			
	#CODE STOLEN FROM CSCOPE.PY ... this should be cleaned up to by by whole months or weeks or something...then functionized.
	#determine first and last dates where the cvs was modified
	s = "select datetime from files order by datetime"
	firstDateString = db.query(s).getresult()[0][0]
	print "first datetime is:", firstDateString
	
	s = "select datetime from files order by datetime desc "
	lastDateString = db.query(s).getresult()[0][0]
	print "last datetime is:", lastDateString
	
	#create python date objects from sql date strings, ex date: 1999-06-18 *timenumbers*
	firstDate = datetime.date(int(firstDateString[:4]), int(firstDateString[5:7]), int(firstDateString[8:10]))
	lastDate = datetime.date(int(lastDateString[:4]), int(lastDateString[5:7]), int(lastDateString[8:10]))
	
	#END CODE STEAL
	
	monthList = getMonthList(firstDate, lastDate)
	
	errors = file("errors", 'w')	
	print >> errors, "start error file"
	
	#loop over all, starting at 2nd element in list
	dateIndex = 1
	while(dateIndex < len(monthList)):

		print monthList[dateIndex].isoformat()
		#update cvs to date
		cvs.updateToDate(monthList[dateIndex], CVSPATH)
		#loop over all files for this datetime interval
		#NOTE: date(datetime) just cuts off the house stuff, if you use a <= and not a <, you will change things ON THAT DATE
		#but CVS has only been updated to midnight the day before that date
		s = "select distinct filename from files where date(datetime) < '%s' and date(datetime) > '%s' and revision similar to '[0-9]+\.[0-9]+' and sourcefile = True" % (monthList[dateIndex].isoformat(), monthList[dateIndex-1].isoformat())
		
		for filename in db.query(s).getresult():
			#get most recent fileid for this file in the time interval
			s = "select fileid from files where filename = '%s' and date(datetime) < '%s' and revision similar to '[0-9]+\.[0-9]+' order by datetime desc limit 1" % (filename[0], monthList[dateIndex].isoformat())
			#print s
			fileid = db.query(s).getresult()[0][0]
			s = "select startline, funcid from functions where fileid = %d  order by startline" % fileid
			linesAndIDs = db.query(s).getresult()
		
			functions = []
			#create a list of functions filled with sql data for lines/ids
			for i in range(len(linesAndIDs)):
				f = Function()
				f.startLine = linesAndIDs[i][0]
				f.funcID = linesAndIDs[i][1]
				functions.append[f]
				

			s = "select filename from files where fileid = %d " % fileid
			path = CVSPATH + db.query(s).getresult()[0][0]
			#print path
			print "parsing file:", path, "fileid:", fileid
			try:
				functions = getInterfaceTokens(file(path), functions)
			except IOError:
				#this means the file was not there...log this for now. 
				print "*** error, file:", path, "was not found at date:",  monthList[dateIndex].isoformat()
				#this can happen if the current date snapshot does not include the file. 

			#convert the interface from a list of tokens to a flat string
			for func in functions:
				interface = ''
				for t in functions.interface:
					interface = interface + t + ' '
				#print "filename: %s, fileid: %d, tokens:%s" % (path, fileid, interface)
				
				s = "update functions set interface = '%s' where funcid = %d" % (interface.encode("string_escape"), func.funcid)
				try:
					db.query(s)
				except pg.ProgrammingError:
					print "*** ERROR, statement caused pg error!"
					print s
					print >> errors, s
				#print s
		dateIndex += 1
	errors.close()


def fillInterfaceChanges(db, log):
	"accesses interface, function and file tables to generate a table explaining when interfaces have changed."
	s = "select distinct filename from files and sourcefile = True"
	for fileName in db.query(s).getresult():
		print fileName[0]
		s = "select distinct functions.name from functions, files where functions.fileid = files.fileid and files.filename = '%s'" % fileName[0]
		for funcName in db.query(s).getresult():
			print funcName[0]
			
			
			s = "select functions.funcid, functions.interface from functions, files where functions.name = '%s' and files.filename = '%s' and files.fileid = functions.fileid and revision similar to '[0-9]+\.[0-9]+' order by files.datetime" % (funcName[0], fileName[0])
			#print s
			results = db.query(s).getresult()
			
			#print results
			if results == []:
				continue
			
			i = 2
			while i <= len(results):
				#compare two interfaces checking for a change
				if results[i-2][1] != results[i-1][1]:
					s = "insert into interfacechanges (origfuncid, newfuncid, datetime) VALUES(%d,%d,(select distinct datetime from files, functions where functions.funcid = %d and functions.fileid = files.fileid))" % (results[i-2][0], results[i-1][0], results[i-1][0])
					print results[i-2][1]
					print results[i-1][1]
					db.query(s)
				i+=1
			
#print "connecting to database.."			
#path = "../../projects/apache/cvs/httpd-2.0/"
#db = _pg.connect(dbname='apacheTest',host='localhost',user='python')
#print "...OK!"

#fillInterfaceData(db, path)
#(db, , ":pserver:alex@localhost/home2/cvsroot/"
#fillInterfaceChanges(db)

def testParser():
	testLines = ["int * foo(char *** bar, void * baz(int, char, xyz))"]
	testLines.append("int foo(char *** bar, void * baz(int arg1, char arg2, xyz))")
	testLines.append("static int * foo(char bar, void * baz)")
	testLines.append("const int foo(const char *** bar, struct S * (baz(int, char, xyz))")
	testLines.append("int foo(char *** bar, void (*baz)(int, char, xyz))")
	testLines.append("""#define FOO(int asdf, const int asdfas) BAR(int asdfasd, int asdfasdfa)""")
	testLines.append("""PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c)
{""")
	testLines.append("""PROXY_DECLARE(int, (char *)(())) ap_proxy_ssl_disable(conn_rec *c)
{
""")
	testLines.append("""DAV_DECLARE(void) dav_register_liveprop_namespace(apr_pool_t *p,
                                                  const char *uri)
{
""")
	for line in testLines:
		print line
		print parseCTokens(line)
		print ''
